﻿using System;
using System.Linq;
using System.Windows;
using System.Collections.ObjectModel;
using System.Collections.Generic;

using PasswordProvider.ViewModel.Helpers;
using PasswordProvider.ObjectModel;
using PasswordProvider.ViewModel.Properties;
using PasswordProvider.ViewModel.Framework;
using PasswordProvider.ViewModel.Service;
using System.Globalization;
using System.Runtime.InteropServices;

namespace PasswordProvider.ViewModel
{
    internal abstract class SiteListItemViewModel : ListViewItemViewModel
    {
        #region Factory Method

        /// <summary>
        /// Create the appropriate concrete instance of SiteListItemViewModel for the given site.
        /// </summary>
        public static SiteListItemViewModel Create(Site site)
        {
            // NOTE: FxCop warns about performance issue when using the as/is pattern
            // because of duplicate cast operation on same instance. So, I'll play along
            // and cashe the result of the first cast.

            LogOnSite logon = site as LogOnSite;
            if(logon != null)
                return new LogOnSiteListItemViewModel(logon);
            
            CreditCardSite credit = site as CreditCardSite;
            if(credit != null)
                return new CreditCardListItemViewModel(credit);

            GenericSite generic = site as GenericSite;
            if (generic != null)
                return new GenericSiteListItemViewModel(generic);

            // Error, this site type is not being handled.
            throw new InvalidOperationException(site.GetType().FullName);            
        }

        #endregion

        #region Events
        /// <summary>
        /// Global notification that the tags property has changed on an item, which affects
        /// the display of the entire site listing if grouping by tag. 
        /// </summary>
        public static event EventHandler TagsChanged;
        #endregion

        #region Fields and Construction

        private Site _site;
        internal SiteListItemViewModel(Site site)
            : base()
        {
            if (site == null)
                throw new ArgumentNullException("site");
            _site = site;
            Guid = site.Guid;
            
            UpdateEncryptedValueBasedPropeties();

        }

        #endregion // Fields and Construction

        #region Abstract

        
        /// <summary>
        /// Inheritors should provide a group display name for their site.
        /// </summary>
        public abstract string GroupDisplayName { get; }
        
        /// <summary>
        /// Inheritors should navigate to the details page for the site type they are wrapping
        /// </summary>
        protected abstract INotifyChangesCommited GoToSiteDetails();
        
        /// <summary>
        /// Inheritors should generate a tooltip string for their site.
        /// </summary>
        protected abstract string GenerateToolTip();
        
        /// <summary>
        /// Inheritors should generate the display name for their site.
        /// </summary>
        protected abstract string GenerateDisplayName();

        

        #endregion

        #region Public Properties       

        /// <summary>
        /// Override DisplayName to generate it dynamically when first accessed.
        /// </summary>
        public override string DisplayName
        {
            get
            {
                if (base.DisplayName == null)
                    base.DisplayName = GenerateDisplayName();
                return base.DisplayName;
            }
            set
            {
                base.DisplayName = value;
            }
        }
        /// <summary>
        /// Override ToolTip to generate it dynamically when first accessed.
        /// </summary>
        public override string ToolTip
        {
            get
            {
                if (base.ToolTip == null)
                    base.ToolTip = GenerateToolTip();
                return base.ToolTip;
            }
            set
            {
                base.ToolTip = value;
            }
        }

        /// <summary>
        /// Site's Guid.
        /// </summary>
        public Guid Guid
        {
            get;
            private set;
        }

        /// <summary>
        /// Tags
        /// </summary>
        public Collection<string> Tags
        {
            get 
            {
                if (_tags == null)
                    _tags = Site.Tags;
                return _tags; 
            }
            set
            {
                if (SetPropertyValue(value, ref _tags, "Tags"))
                {
                    if (TagsChanged != null)
                        TagsChanged(this, new EventArgs());
                }
            }
        } private Collection<string> _tags;

        /// <summary>
        /// Returns the lowest tag in the Tags list, used for sorting the groups when grouping by tag. 
        /// </summary>
        public string ComparativelyLowTag
        {
            get
            {
                // Return ZZZ to force empty groups to be sorted as high, so they appear on bottom of list.
                return Tags.Count > 0 ? Tags.Min() : "ZZZZZZZZ";
            }
        }

        
        /// <summary>
        /// Access count
        /// </summary>
        public int AccessCount
        {
            get { return _accessCount; }
            set
            {
                SetPropertyValue(value, ref _accessCount, "AccessCount");
            }
        } protected int _accessCount;

        /// <summary>
        /// Last Access date. Used as a sort criteria by SitesListsViewModel.
        /// </summary>
        public DateTime LastAccessDate
        {
            get { return _lastAccessDate; }
            set
            {
                SetPropertyValue(value, ref _lastAccessDate, "LastAccessDate");
            }
        } protected DateTime _lastAccessDate;


        /// <summary>
        /// New Site command.
        /// </summary>
        public CommandViewModel ShowSitePropertiesCommand
        {
            get
            {
                if (_showSitePropertiesCommand == null)
                {
                    _showSitePropertiesCommand = new CommandViewModel(
                        null, String.Format(CultureInfo.CurrentCulture, Strings.SiteListViewItem_GoToSiteButton_ToolTip, _site.Name),
                        new RelayCommand(param => ShowSitePropertiesCommandExecuted()));
                    _showSitePropertiesCommand.Visibility = Visibility.Collapsed;

                }
                return _showSitePropertiesCommand;
            }
        } private CommandViewModel _showSitePropertiesCommand;


        /// <summary>
        /// Navigate to web site command. 
        /// </summary>
        public CommandViewModel NavigateToWebSiteCommand
        {
            get
            {
                if (_navigateToWebSiteCommand == null)
                {
                    _navigateToWebSiteCommand = new CommandViewModel(
                        null,
                        new RelayCommand(param => NavigateToWebSiteCommandExecuted(), param => NavigateToWebSiteCommandCanExecute()));
                }
                return _navigateToWebSiteCommand;
            }
        } private CommandViewModel _navigateToWebSiteCommand;

        #endregion // Public Properties

        #region Protected

        /// <summary>
        /// Access the site.
        /// </summary>
        protected Site Site
        {
            get { return _site; }
        }

        protected virtual void UpdateDisplay()
        {
            UpdateEncryptedValueBasedPropeties();
            ToolTip = GenerateToolTip();
            DisplayName = GenerateDisplayName();
        }
        
        #endregion // Protected

        #region Command Handlers

        private void ShowSitePropertiesCommandExecuted()
        {            
            // Go to the site details.
            INotifyChangesCommited vm = GoToSiteDetails();
            
            // Create an event handler to update the display properties after the site has been edited.
            EventHandler handler = null;
            handler = delegate
            {
                UpdateDisplay(); // Update properties for display
                vm.ChangesCommitted -= handler; // remove the event handler
            };
            // Add the event handler
            vm.ChangesCommitted += handler;

            this.IsSelected = true;
        }

        /// <summary>
        /// Handler for the NavigateToWebsiteCommand. Default implementation does nothing. 
        /// Derived classes should override this method and call NavigateToWebSite with their desired destination Url.
        /// </summary>
        protected virtual void NavigateToWebSiteCommandExecuted()
        {
        }

        /// <summary>
        /// Determine whether the navigate to web site command should be enabled. Default value is false.
        /// </summary>
        protected virtual bool NavigateToWebSiteCommandCanExecute()
        {
            return false;
        }

        /// <summary>
        /// Method to navigate to a web site
        /// </summary>
        protected void NavigateToWebSite(LogOnSite webSite)
        {
            if (webSite == null)
                throw new ArgumentNullException("webSite");

            if (webSite.Url == null)
                return;

            if (!Settings.Default.DoubleClickNavigatesToWebSite)
                return;

            bool usingInternetExplorer =
                (Settings.Default.AlwaysUseInternetExplorer || IENavigationHelper.IsIETheDefaultBrowser());

            // Need to be authenticated if going to attempt to autofill.
            if (usingInternetExplorer && Settings.Default.AttemptAutoFill && !PasswordProviderData.Current.SecurityManager.IsAuthenticated)
            {
                // Need authentication to continue.
                NavigationManager.GoToEnterPasswordView(true,
                    (d) =>
                    { // When authentication is complete, re-call this method.
                        if (PasswordProviderData.Current.SecurityManager.Authenticate(d))
                        {
                            NavigateToWebSite(webSite);
                            return true;
                        }
                        return false;
                    });
                return;
            }

            if (usingInternetExplorer)
            {
                try
                {
                    mshtml.HTMLDocument doc = IENavigationHelper.OpenUrl(webSite.Url);
                    if (doc != null)
                    {
                        // Now attempt to fill the browser.            
                        if (Settings.Default.AttemptAutoFill)
                        {
                            string loginId = webSite.LogOnId;
                            IEHtmlDocumentHelper.AttemptFillPasswordAndLogin(doc, loginId, webSite.Guid);
                        }
                    }
                }
                catch (COMException)
                {
                    // Ignore COM exception, these can happen if, for example, the user closes the browser window before
                    // the page has completed loading. 

                }
            }
            else // not using IE, using the default.
            {
                System.Diagnostics.Process.Start(webSite.Url.AbsoluteUri);
            }
        }
        
        #endregion

        #region Private Helpers

        private void UpdateEncryptedValueBasedPropeties()
        {
            EncryptedValue encrypted = Site.PrimaryEncryptedValue;
            if (encrypted != null)
            {
                LastAccessDate = encrypted.LastAccessDate;
                AccessCount = encrypted.AccessCount;
                // While we have a handle on it, add a handler to the encrypted value PropertyChanged event
                // for the purpose of updating the display.
                encrypted.PropertyChanged +=
                    delegate
                    {
                        LastAccessDate = encrypted.LastAccessDate;
                        AccessCount = encrypted.AccessCount;
                        ToolTip = GenerateToolTip();
                    };
            }
        }

        #endregion

    }



}
